import cv2
import numpy as np

import myutils


def cv_show(name, img):
    """
    展示图片
    :param name: 图片名称
    :param img:  图片
    """
    cv2.imshow(name, img)
    cv2.waitKey(0)
    cv2.destroyAllWindows()


# 模板图片
tem = './images/ocr_a_reference.png'
# 银行卡图片
card = './images/credit_card_01.png'
# card = './images/8fed9f19f8f70c5fc0d11dd1011a09f.jpg'

# 处理数字模板, 将数字从模板上扣下来, 与0-9的数字做一个关联
# 读取数字模板
img = cv2.imread(tem)
cv_show('tem', img)

# 将模板转成灰度图
ref = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)
cv_show('tem_gray', ref)

# 灰度图二值处理
ref = cv2.threshold(ref, 10, 255, cv2.THRESH_BINARY_INV)[1]
cv_show('binary_inv', ref)

# 计算轮廓
ref_, ref_cnts, hierarchy = cv2.findContours(ref.copy(), cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE)
# 绘制轮廓
cv2.drawContours(img, ref_cnts, -1, (0, 0, 255), 3)
cv_show('img', img)

# 对轮廓进行排序 --> 用矩形将轮廓包裹, 根据矩形排序 --> 结果是排序之后的矩形
ref_cnts = myutils.sort_contours(ref_cnts, method="left-to-right")[0]
# 遍历轮廓, 截取每一个数字的模板
digits = {}  # key 为数字, value为对应的数字图片
for (i, c) in enumerate(ref_cnts):
    # 计算轮廓的外接矩形
    x, y, w, h = cv2.boundingRect(c)
    # 在模板上截取数字的区域
    roi = ref[y:y + h, x: x + w]
    # 调整数字图片的大小
    roi = cv2.resize(roi, (57, 88))
    # 每一个数字对应一个模板
    digits[i] = roi

# 处理银行卡图片

# 设置卷积核
rectKernel = cv2.getStructuringElement(cv2.MORPH_RECT, (9, 3))  # 卡号数字块的核
sqKernel = cv2.getStructuringElement(cv2.MORPH_RECT, (5, 5))

# 读取银行卡
card_image = cv2.imread(card)
cv_show('img', card_image)
card_image = myutils.resize(card_image, width=300)
# 灰度图
card_gray = cv2.cvtColor(card_image, cv2.COLOR_BGR2GRAY)
cv_show('gray', card_gray)

# 突出卡号等细小的区域, 去除大块卡片上的杂乱背景 --> 礼帽操作
tophat = cv2.morphologyEx(card_gray, cv2.MORPH_TOPHAT, rectKernel)
cv_show('tophat', tophat)

# Sobel算子计算图像梯度, 检测边缘 --> x方向上的边缘
grad_x = cv2.Sobel(tophat, ddepth=cv2.CV_32F, dx=1, dy=0, ksize=-1)
# 边缘取绝对值, 然后做归一化处理
cv_show('grad_x', grad_x)
grad_x = np.absolute(grad_x)
cv_show('grad_x_abs', grad_x)
min_val = np.min(grad_x)
max_val = np.max(grad_x)
grad_x = (255 * ((grad_x - min_val) / (max_val - min_val)))
cv_show('grad_x', grad_x)
# 将结果转为8位的无符号数, 否则后续处理会报错
grad_x = grad_x.astype('uint8')
cv_show('grad_x', grad_x)

# 闭操作 -- > 让卡号连成一个区域
grad_x = cv2.morphologyEx(grad_x, cv2.MORPH_CLOSE, rectKernel)
cv_show('grad_x_close', grad_x)
# 二值化处理, 之后做轮廓检测
thresh = cv2.threshold(grad_x, 0, 255,
                       cv2.THRESH_BINARY | cv2.THRESH_OTSU)[1]
cv_show('thresh', thresh)

# 卡号块内有黑条纹, 使用闭操作去除卡号块内的黑条
thresh = cv2.morphologyEx(thresh, cv2.MORPH_CLOSE, rectKernel)
cv_show('thresh', thresh)

# 计算轮廓, 扩区卡号亮块的轮廓
thresh_, thresh_cnts, hierarchy = cv2.findContours(thresh.copy(), cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE)

# 绘制轮廓
cur_img = card_image.copy()
cv2.drawContours(cur_img, thresh_cnts, -1, (0, 0, 255), 3)
cv_show('img', cur_img)

locs = []

# 遍历轮廓进行筛选 --> 根据比例筛选出卡号数字区域
for (i, c) in enumerate(thresh_cnts):
    # 绘制外接矩形
    x, y, w, h = cv2.boundingRect(c)
    ar = w / float(h)

    if ar > 2.5 and ar < 4.0:
        if (w > 40 and w < 55) and (h > 10 and h < 20):
            # 符合的留下来
            locs.append((x, y, w, h))

# 对外接矩形进行排序 . 根据x的值进行排序
locs = sorted(locs, key=lambda item: item[0])

output = []

# 遍历每一个卡号数字块的矩形
for (i, (gx, gy, gw, gh)) in enumerate(locs):
    group_output = []

    # 从灰度图中截取轮廓区域, 多截取一点
    group = card_gray[gy - 5:gy + gh + 5, gx - 5:gx + gw + 5]
    cv_show('group', group)

    # 二值处理
    group = cv2.threshold(group, 0, 255,
                          cv2.THRESH_BINARY | cv2.THRESH_OTSU)[1]
    cv_show('group_binary', group)

    # 计算轮廓, 每一个数字的轮廓
    group_, digit_cnts, hierarchy = cv2.findContours(group.copy(), cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE)
    # 给轮廓排序
    digit_cnts = myutils.sort_contours(digit_cnts, method="left-to-right")[0]

    # 遍历轮廓, 从中截取到数字的区域, 与1-10的模板进行对比
    for c in digit_cnts:
        (x, y, w, h) = cv2.boundingRect(c)  # 计算外接矩形
        # 从二值处理后的图片中截取数字
        roi = group[y:y + h, x:x + w]
        roi = cv2.resize(roi, (57, 88))
        cv_show('roi', roi)

        # 计算匹配得分
        scores = []
        for key, value in digits.items():
            # 匹配模板, 计算相关系数, 值越大越相关
            result = cv2.matchTemplate(roi, value, cv2.TM_CCOEFF)
            (_, score, _, _) = cv2.minMaxLoc(result)  # 取最大值
            scores.append(score)

        # 追加最匹配模板的值
        # 获取的是分数最高的元素的索引值, 索引值就是匹配的数字
        group_output.append(str(np.argmax(scores)))

    # 绘制长方形, 框住卡号数字块
    cv2.rectangle(card_image, (gx - 5, gy - 5), (gx + gw + 5, gy + gh + 5), (0, 0, 255), 1)
    cv2.imshow("Image", card_image)
    # 把数字绘制到卡号上
    # 0.65比哦啊是字体大小, 2是线宽
    cv2.putText(card_image, "".join(group_output), (gx, gy - 15), cv2.FONT_HERSHEY_SIMPLEX, 0.65, (0, 0, 255), 2)

    # 得到结果
    output.extend(group_output)

# 打印结果
print("Credit Card #: {}".format("".join(output)))
cv2.imshow("Image", card_image)
cv2.waitKey(0)
